#include "method/shading.h"


#include <cstdlib>
#include <algorithm>
#include <set>

#include <QWidget>
#include <QPainter>
#include <QImage>

// TODO remover include
#include "model/regular.h"
#include <QDebug>



void ConstantShading::draw(QWidget *container, Scene *projection) {
    this->window2viewport(container, projection);

    unsigned int container_width = container->width();
    unsigned int container_height = container->height();
    unsigned int data_length = container_width*container_height;

    QRgb background = qRgb(241, 241, 241);

    uchar *pixels_data = new uchar[data_length*4];
    QRgb (*pixels)[container_width] = reinterpret_cast<QRgb(*)[container_width]>(pixels_data);
    std::fill( &pixels[0][0], &pixels[0][0] + data_length, background);

    // GAMBIARRA 1: Declarando a matrix de uma maneira que não buga tudo
    coord *ddata = new coord[data_length];
    coord (*distance)[container_width] = reinterpret_cast<double(*)[container_width]>(ddata);
    std::fill( &distance[0][0], &distance[0][0] + data_length, coord_max );

    for ( const Object* object: projection->getObjects() ) {
        if (object->isVisible()) {
            for ( const Face* face: object->getFaces()) {

                QRgb color = glow(projection, object->getLightProperties(), face->getCenter(), face->getNormalVector());;

                int lower = INT_MAX;
                int greater = INT_MIN;

                std::map<int, std::list<Edge>> rows;
                std::list<Edge> buffer;
                const std::vector<const HalfEdge*> &edges = face->edges();

                for ( const HalfEdge* edge: edges ) {
                    Vertex begin = *edge->begin,
                            end = *edge->end;

                    if ( begin.y > end.y )
                        std::swap(begin, end);

                    int ymin = (int)ceil(begin.y);
                    int ymax = (int)end.y;

                    if ( lower > ymin )
                        lower = ymin;

                    if ( greater < ymax )
                        greater = ymax;

                    rows[ymin].push_back(Edge(begin, end));
                }

                for ( int row = lower; row <= greater; row++ ) {
                    const std::list<Edge> &edges = rows[row];
                    buffer.insert(buffer.begin(), edges.begin(), edges.end());

                    for( auto i = buffer.begin(); i != buffer.end(); ) {
                        if ( i->max <= row ) {
                            buffer.erase(i++);
                            continue;
                        }
                        i++;
                    }

                    buffer.sort();

                    for( auto i = buffer.begin(); i != buffer.end(); ) {
                        Edge &begin = *(i++);
                        Edge &end = *(i++);

                        coord z = begin.z;
                        coord width = end.x - begin.x;
                        coord iz = (end.z - begin.z)/width;

                        int xmax = end.x;
                        int xmin = begin.x;

                        if ( row > 0 && row < (int)container_height ) {
                            for ( int column = ceil(xmin); column < xmax; column++ ) {
                                if ( column > 0 && column < (int)container_width && distance[row][column] > z ) {
                                    distance[row][column] = z;
                                    pixels[row][column] = color;
                                }
                                z += iz;
                            }
                        }

                        ++begin;
                        ++end;
                    }
                }
            }
        }
    }

    QImage image = QImage(pixels_data, container_width, container_height, QImage::Format_RGB32);
    QPainter painter(container);
    painter.drawImage(0, 0, image);

    delete ddata;
    delete pixels_data;
}


QRgb ConstantShading::glow(const Scene *s, const Lighting &properties, const Point &point, const Point &normalVector) {

    Scene::Light enviromentLight = s->getProperties();

    light_t iar = enviromentLight.ilar * properties.kar;
    light_t iag = enviromentLight.ilag * properties.kag;
    light_t iab = enviromentLight.ilab * properties.kab;

    const Point l = (enviromentLight.lightSource - point).unit();
    light_t alfa = l*normalVector;

    if (alfa < 0  || alfa >180)
        return qRgb(std::min(255,(int)iar), std::min(255,(int)iag), std::min(255,(int)iab));;

    alfa*=2;
    Vertex R = (normalVector*alfa)-l;

    Vertex S = (Vertex(0,0,0)-point).unit();

    light_t ir = iar + ( enviromentLight.ilr * (properties.kdr * alfa + properties.ksr * pow(R*S, properties.n)));
    light_t ig = iag + ( enviromentLight.ilg * (properties.kdg * alfa + properties.ksg * pow(R*S, properties.n)));
    light_t ib = iab + ( enviromentLight.ilb * (properties.kdb * alfa + properties.ksb * pow(R*S, properties.n)));

    return qRgb(std::min(255,(int)ir), std::min(255,(int)ig), std::min(255,(int)ib));;
}

void GouraudShading::draw(QWidget *container, Scene *projection) {
    //    Object* object = projection->getGround();
    this->window2viewport(container, projection);

    unsigned int container_width = container->width();
    unsigned int container_height = container->height();
    unsigned int data_length = container_width*container_height;

    QRgb background = qRgb(241, 241, 241);

    uchar *pixels_data = new uchar[data_length*4];
    QRgb (*pixels)[container_width] = reinterpret_cast<QRgb(*)[container_width]>(pixels_data);
    std::fill( &pixels[0][0], &pixels[0][0] + data_length, background);

    // GAMBIARRA 1: Declarando a matrix de uma maneira que não buga tudo
    coord *ddata = new coord[data_length];
    coord (*distance)[container_width] = reinterpret_cast<double(*)[container_width]>(ddata);
    std::fill( &distance[0][0], &distance[0][0] + data_length, coord_max );

    for ( const Object* object: projection->getObjects() ) {

        if (!object->isVisible()) continue;

        const std::vector <const Face*> &faces = object->getFaces();
        std::vector <Point> faceNormalVector(faces.size());

        for (const Face* face: faces) {
            faceNormalVector[face->index] = face->getNormalVector();
        }

        const std::vector<const Vertex*> &vertexes = object->getVertex();
        std::vector<QRgb> colors(vertexes.size());

        for (const Vertex* vertex : vertexes) {

            const std::list<const Face*> &vertexFaces = vertex->getFaces();
            Point sum;
            for (const Face* face: vertexFaces) {
                sum = sum+faceNormalVector[face->index];
            }

            colors[vertex->index] = glow(projection, object->getLightProperties(), *vertex, sum/vertexFaces.size());
        }

        for ( const Face* face: object->getFaces()) {

            int lower = INT_MAX;
            int greater = INT_MIN;

            std::map<int, std::list<GouroudEdge>> rows;
            std::list<GouroudEdge> buffer;
            const std::vector<const HalfEdge*> &edges = face->edges();

            for ( const HalfEdge* edge: edges ) {
                Vertex begin = *edge->begin;
                Vertex end = *edge->end;

                if ( begin.y > end.y )
                    std::swap(begin, end);

                int ymin = (int)ceil(begin.y);
                int ymax = (int)end.y;

                if ( lower > ymin )
                    lower = ymin;

                if ( greater < ymax )
                    greater = ymax;

                rows[ymin].push_back(GouroudEdge(begin, end, colors[begin.index], colors[end.index]));
            }

            for ( int row = lower; row <= greater; row++ ) {
                const std::list<GouroudEdge> &edges = rows[row];
                buffer.insert(buffer.begin(), edges.begin(), edges.end());

                for( auto i = buffer.begin(); i != buffer.end(); ) {
                    if ( i->max <= row ) {
                        buffer.erase(i++);
                        continue;
                    }
                    i++;
                }

                buffer.sort();

                for( auto i = buffer.begin(); i != buffer.end(); ) {
                    GouroudEdge &begin = *(i++);
                    GouroudEdge &end = *(i++);

                    coord z = begin.z;
                    light_t r = begin.r;
                    light_t g = begin.g;
                    light_t b = begin.b;

                    coord width = end.x - begin.x;

                    coord iz = (end.z - begin.z)/width;
                    light_t ir = (end.r-begin.r)/width;
                    light_t ig = (end.g-begin.g)/width;
                    light_t ib = (end.b-begin.b)/width;

                    int xmax = end.x;
                    int xmin = begin.x;

                    if ( row > 0 && row < (int)container_height ) {
                        for ( int column = ceil(xmin); column < xmax; column++ ) {
                            if ( column > 0 && column < (int)container_width && distance[row][column] > z ) {
                                distance[row][column] = z;
                                pixels[row][column] = qRgb(std::min(255,(int)r), std::min(255,(int)g), std::min(255,(int)b));
                            }
                            z += iz;
                            r += ir;
                            g += ig;
                            b += ib;
                        }
                    }

                    ++begin;
                    ++end;
                }
            }
        }
    }

    QImage image = QImage(pixels_data, container_width, container_height, QImage::Format_RGB32);
    QPainter painter(container);
    painter.drawImage(0, 0, image);

    delete ddata;
    delete pixels_data;
}

